package com.example.mydianping.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import com.example.mydianping.dto.LoginFormDTO;
import com.example.mydianping.dto.Result;
import com.example.mydianping.dto.UserDTO;
import com.example.mydianping.mapper.UserMapper;
import com.example.mydianping.pojo.User;
import com.example.mydianping.service.UserService;
import com.example.mydianping.utils.UserHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result login(LoginFormDTO loginFormDTO, HttpSession session) {
        if(loginFormDTO == null){
            return Result.fail("无效操作");
        }
        //校验手机号
        String phone = loginFormDTO.getPhone();
        if (phone == null || !PhoneUtil.isPhone(phone)) {
            return Result.fail("请输入合法的手机号");//若不合法直接响应错误
        }
        //拿到 session 域中的验证码
        //TODO 优化：从 redis 中获取验证码
        String code1 = stringRedisTemplate.opsForValue().get("login:code:"+phone);
        String code = loginFormDTO.getCode();
        if(!code.equals(code1)){
           return Result.fail("验证码输入错误，请重新输入");
        }
        //验证码正确，判断是否存在用户
        User user = userMapper.selectByPhone(phone);
        if(user == null){
            //若不存在就创建一个用户并存入 mysql
            user = createUserByPhone(phone);
            userMapper.insert(user);
        }
        //保存用户登录信息
        //TODO 优化：生成 token ,使用哈希的方式保存用户信息
        //使用 hutool 的 UUID 来生成 token
        String token = UUID.randomUUID().toString();
        UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
        //使用 stringRedisTemplate，使用 hash 的方式存储存信息时必须保证所有 key value 都是 String 类型
        Map<String,String> userMap = new HashMap<>();
        userMap.put("id",Long.toString(userDTO.getId()));
        userMap.put("nickName",userDTO.getNickName());
        userMap.put("icon",userDTO.getIcon());
        stringRedisTemplate.opsForHash().putAll("login:token:"+token,userMap);
        //设置 token 有效日期，此处设定初始有效日期，可以通过通用拦截器更新有效日期
        stringRedisTemplate.expire("login:token:"+token,30,TimeUnit.MINUTES);
        //TODO 优化：返回生成的 token
        return Result.ok(token);
    }

    private User createUserByPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName("user_"+RandomUtil.randomString(10));
        stringRedisTemplate.opsForHyperLogLog().add("UV",phone);//UV统计
        return user;
    }

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //先用 hutool 工具校验手机号是否合法
        if (phone == null || !PhoneUtil.isPhone(phone)) {
            return Result.fail("请输入合法的手机号");//若不合法直接响应错误
        }
        //用 hutool 工具生成六位验证码
        String code = RandomUtil.randomNumbers(6);
        //将生成的验证码放入 session
        //TODO 优化：保存验证码到redis
        //TODO 后面两个参数时验证码的有效期，2分钟，设置后 redis 会在底层加上 set key value ex 120
        stringRedisTemplate.opsForValue().set("login:code:"+phone,code,2, TimeUnit.MINUTES);
        //给手机号发送验证码，这里模拟发送验证码的操作，而不是真正的发送
        System.out.println("手机收到了一条验证码短信："+code);
        return Result.ok();
    }

    @Override
    public Result userSign() {
        UserDTO user = UserHolder.getUser();
        if(user == null){
            return Result.fail("请先登录");
        }
        //获取年月作为 key
        LocalDateTime now = LocalDateTime.now();
        String yearAndMonth = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
        String key = "sign:"+user.getId()+":"+yearAndMonth;
        //获取天数完成签到操作
        int dayOfMonth = now.getDayOfMonth();//起始值为 1
        stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
        updateLink(user.getId(),now);
        return Result.ok();
    }
    //更新连续签到天数
    public void updateLink(Long userId,LocalDateTime now){
        //先查询前一天是否成签到
        LocalDateTime yesterday = now.minusHours(24);//当前时间减去 24 小时
        String yearAndMonth = yesterday.format(DateTimeFormatter.ofPattern("yyyyMM"));
        String key = "sign:"+userId+":"+yearAndMonth;
        int dayOfMonth = yesterday.getDayOfMonth();
        Boolean isSing = stringRedisTemplate.opsForValue().getBit(key, dayOfMonth-1);//这里一定要记得 -1 我一开始忽略了导致程序出错，找了半天
        String saveKey = "user:sign:keep:"+userId;
        System.out.println(isSing + " "+yearAndMonth+" "+(dayOfMonth-1));
        if(isSing == null || !isSing){
            //当前用户第一次签到,或未连续签到
            stringRedisTemplate.opsForValue().set(saveKey,"1");
            return;
        }
        //昨天签到过，连续签到次数继续累加
        String s = stringRedisTemplate.opsForValue().get(saveKey);
        if(s != null){
            long save = Long.parseLong(s) + 1;//将原来的值加一
            stringRedisTemplate.opsForValue().set(saveKey,Long.toString(save));
        }
    }

}
